package tv.geir.slackspace.space;

import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

import net.jini.core.lease.Lease;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tv.geir.slackspace.common.ProminentObject;
import tv.geir.slackspace.event.SpaceEventListener;
import tv.geir.slackspace.event.SpaceEventListenerList;
import tv.geir.slackspace.matching.Template;
import tv.geir.slackspace.matching.TemplateMatcher;
import tv.geir.slackspace.server.ServerLease;

/**
 *  
 *  An ObjectSpace is used to store entries, ProminentObjects. Manages lease expire
 *  
 * @author Geir Gullestad Pettersen
 *
 */
public class ObjectSpace {
	private Vector<Entry> objects;

	private SpaceEventListenerList listeners;
	
	private static Log log = LogFactory.getLog(ObjectSpace.class);

	private TemplateMatcher matcher = new TemplateMatcher();

	private Thread t;
	
	private long leaseManagerSleepMillis = 120000;
	
	private boolean poolRunning;
	
	public ObjectSpace() {
		poolRunning = true;
		objects = new Vector<Entry>();
		listeners = new SpaceEventListenerList();
	}
	
	

	public void add(ProminentObject mo, ServerLease lease) {
		objects.add(new Entry(mo, lease));
		if( log.isDebugEnabled() ) {
			double expireIn = (double) ( lease.getExpireTime() - System.currentTimeMillis() ) / 1000; 
			log.debug( "Object (lease=" + lease.getLeaseId() + ") written to space, will expire in " + expireIn + " seconds" );
		}
	}

	public boolean remove( ProminentObject mo ) {
		Entry r =  null;
		for( Entry e : objects ) {
			if( e.object == mo) {
				r = e;
			}
		}
		if( r != null ) {
			if( log.isDebugEnabled() ) {
				log.debug( "Removing from space: " + mo );
			}
			return objects.remove( r );
		}
		return false;
	}
	
	public ProminentObject get(int index) {
		return objects.get(index).object;
	}

	/**
	 * Returns an object matching the template and removes it from the vector
	 * @param t
	 * @return
	 */
	public ProminentObject remove(Template t) {
		Entry e = findEntry(t);
		if (e != null) {
			objects.remove(e);
			return e.object;
		}
		return null;
	}

	/**
	 * Returns an object matching the template.
	 * @param t
	 * @return
	 */
	public ProminentObject get(Template t) {
		Entry e = findEntry(t);
		if (e != null) {
			return e.object;
		}
		return null;
	}

	/**
	 * Finds an entry matching the template
	 * @param t
	 * @return
	 */
	private Entry findEntry(Template t) {
		long now = System.currentTimeMillis();
		for (Entry e : objects) {
			if (matcher.isAssignableFrom(e.object.getTemplate(), t) &&
				entryNotExpired( e, now )) {
				return e;
			}
		}
		return null;
	}

	private boolean entryNotExpired( Entry e, long expiretime ) {
		return e.lease.getExpireTime() > expiretime;
	}
	
	public int size() {
		return objects.size();
	}


	private List<Entry> removeExpiredObjects( List<Entry> objects) {
		if( objects.size() == 0 ) {
			// escape if no objects in pool
			if( log.isDebugEnabled() ) {
				log.debug( "No objects in pool, skipping removal" );
			}
			return null ;
		}
		LinkedList<Entry> expired = new LinkedList<Entry>();
		synchronized ( objects ) {		
			for( Entry e : objects ) {
				long now = System.currentTimeMillis();
				if( e.lease.getLeaseTime() != Lease.FOREVER &&
					e.lease.getExpireTime() < now ) {
					if( log.isDebugEnabled() ) {
						log.debug( "Entry expired (lease=" + e.lease.getLeaseId() + ")" );
					}
					expired.add( e );
										
				} else if ( log.isDebugEnabled() ) {
					log.debug( "Entry not expired (lease=" + e.lease.getLeaseId() + "). Expires in " + ((double) e.lease.exiresInMillis() / (double) 1000) + "s"  );
				}
			}
			
			for( Entry r : expired ) {
				objects.remove( r );
			}
			
			// TODO add notification to removal?
		}
		if( log.isDebugEnabled() ) {
			log.debug( expired.size() + " entries expired. " + objects.size() + " entries in pool"  );
		}
		return expired;
		
		
	}
	
	private void startLeaseExpirationManager() {
		t = new Thread( "ExpiredEntryRemover" ) {
			public void run() {
				int seconds = (int) ( leaseManagerSleepMillis / 1000 ) ;
				while( poolRunning ) {
					try {						
						Thread.sleep( leaseManagerSleepMillis );
					} catch (InterruptedException e) {						
						if( !poolRunning ) {
							log.info( "Shutting down LeaseExpirationManager" );
							return;
						}
					}
					List<Entry> expired = removeExpiredObjects( objects );
					if( expired != null ) {
						int count = expired.size();
						if( log.isDebugEnabled() ) {
							log.debug( "Waiting " + seconds + "s for next lease expired cleanup" );
						}
					}
				}
			}
		};
		t.start();
	}
	
	
	private void startLeaseExpirationManager( final List<Entry> objects, final List<Entry> nextGeneration, final long sleepTime ) {
		t = new Thread( "LeaseExpirationManager" ) {
			public void run() {
				int seconds = (int) ( leaseManagerSleepMillis / 1000 ) ;
				while( poolRunning ) {
					try {
						
						Thread.sleep( sleepTime );
					} catch (InterruptedException e) {						
						if( !poolRunning ) {
							log.info( "Shutting down LeaseExpirationManager" );
							return;
						}
					}
					List<Entry> expired = removeExpiredObjects( objects );
					if( expired != null ) {
						int count = expired.size();
						
						log.debug( count + " entries expired." );
						log.debug( "Waiting " + seconds + "s for next lease expired cleanup" );
					}
				}
			}
		};		
	}
	public void stopSpace() {
		poolRunning = false;
		t.interrupt();
	}
	
	class Entry {
		public ProminentObject object;
		
		public ServerLease lease;

		public Entry(ProminentObject po, ServerLease l) {
			this.object = po;
			this.lease = l;						
		}
	}

	/**
	 * TODO IMPLEMENT
	 * @param listener
	 */
	public void addSpaceEventListener(SpaceEventListener listener) {
		//listeners.addSpaceEventListener(listener);
	}



	/**
	 * TODO IMPLEMENT
	 * @param listener
	 */
	public void removeSpaceEventListener(SpaceEventListener listener) {
		//listeners.removeSpaceEventListener(listener);
	}



	public void setLeaseManagerSleepMillis(long leaseManagerSleepMillis) {
		this.leaseManagerSleepMillis = leaseManagerSleepMillis;
	}



	public long getLeaseManagerSleepMillis() {
		return leaseManagerSleepMillis;
	}


}
